package net.akkz.dbFactory.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;

import net.akkz.dbFactory.Session;
import net.akkz.exception.BuildClassError;
import net.akkz.exception.SQLExcuteException;

public class SessionImpl implements Session
{
	private Connection conn;

	public SessionImpl(Connection conn) throws SQLException
	{
		this.conn = conn;
		conn.setAutoCommit(false);
	}

	public void commit()
	{
		try
		{
			if(this.conn != null && !conn.isClosed())
			{
				conn.commit();
				closeConnection(conn);
			}
		}
		catch (SQLException | SQLExcuteException e)
		{
			e.printStackTrace();
		}
	}

	public void rollback()
	{
		try
		{
			if(this.conn != null && !conn.isClosed())
			{
				conn.rollback();
				closeConnection(conn);
			}
		}
		catch (SQLException | SQLExcuteException e)
		{
			e.printStackTrace();
		}
	}

	public void close() throws SQLExcuteException
	{
		try
		{
			if (this.conn != null)
			{
				this.conn.close();
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
	}

	@Override
	public void excute(String sql, Object[] values) throws SQLExcuteException
	{
		PreparedStatement ps = null;

		try
		{
			ps = buildPreparedStatement(sql, values);

			ps.execute();
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closePreparedStatement(ps);
		}
	}
	
	@Override
	public void excute(String sql) throws SQLExcuteException
	{
		Statement state = null;

		try
		{
			state = conn.createStatement();
			
			state.execute(sql);
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeStatement(state);
		}
	}

	@Override
	public <T> T get(String sql, Object[] values, Class<T> clazz) throws SQLExcuteException, BuildClassError
	{
		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = buildPreparedStatement(sql, values);
			rs = ps.executeQuery();

			if (rs.next())
			{
				return BuildObject(rs, clazz);
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return null;
	}

	@Override
	public <T> T get(String sql, int id, Class<T> clazz) throws SQLExcuteException, BuildClassError
	{
		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = conn.prepareStatement(sql);
			ps.setInt(1, id);

			rs = ps.executeQuery();

			if (rs.next())
			{
				return BuildObject(rs, clazz);
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return null;
	}
	

	@Override
	public String get(String sql, String id) throws SQLExcuteException
	{
		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = conn.prepareStatement(sql);
			ps.setString(1, id);

			rs = ps.executeQuery();

			if (rs.next())
			{
				return rs.getString(1);
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return null;
	}

	@Override
	public <T> ArrayList<T> find(String sql, Object[] values, Class<T> clazz) throws SQLExcuteException, BuildClassError
	{
		ArrayList<T> arrays = new ArrayList<T>();

		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = buildPreparedStatement(sql, values);
			rs = ps.executeQuery();

			while (rs.next())
			{
				arrays.add(BuildObject(rs, clazz));
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return arrays;
	}
	
	@Override
	public <T> ArrayList<T> find(String sql, Object[] values, Class<T> clazz, int start, int length) throws SQLExcuteException, BuildClassError
	{
		ArrayList<T> arrays = new ArrayList<T>();

		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = buildPreparedStatement(sql + " limit ?,?", values);
			
			ps.setInt(values.length + 1, start);
			ps.setInt(values.length + 2, length);
			
			rs = ps.executeQuery();

			while (rs.next())
			{
				arrays.add(BuildObject(rs, clazz));
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return arrays;
	}

	@Override
	public boolean check(String sql, Object[] values) throws SQLExcuteException
	{
		PreparedStatement ps = null;
		ResultSet rs = null;

		try
		{
			ps = buildPreparedStatement(sql, values);

			rs = ps.executeQuery();

			if (rs.next())
			{
				return true;
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
		finally
		{
			closeResultSet(rs);
			closePreparedStatement(ps);
		}

		return false;
	}

	private PreparedStatement buildPreparedStatement(String sql, Object[] values) throws SQLException
	{
		PreparedStatement ps = conn.prepareStatement(sql);

		if (values != null)
		{
			for (int i = 0; i < values.length; i++)
			{
				if(values[i] instanceof Integer)
					ps.setInt(i+1, (int) values[i]);
				else if(values[i] instanceof String)
					ps.setString(i+1, (String)values[i]);
			}
		}

		return ps;
	}

	private void closePreparedStatement(PreparedStatement ps) throws SQLExcuteException
	{
		try
		{
			if (ps != null)
			{
				ps.close();
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
	}

	private void closeStatement(Statement state) throws SQLExcuteException
	{
		try
		{
			if (state != null)
			{
				state.close();
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
	}
	
	private void closeResultSet(ResultSet rs) throws SQLExcuteException
	{
		try
		{
			if (rs != null)
			{
				rs.close();
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
	}

	private void closeConnection(Connection conn) throws SQLExcuteException
	{
		try
		{
			if (conn != null)
			{
				conn.close();
			}
		}
		catch (SQLException e)
		{
			throw new SQLExcuteException(e);
		}
	}

	private <T> T BuildObject(ResultSet rs, Class<T> clazz) throws BuildClassError
	{
		try
		{
			T t = clazz.newInstance();
			
			ResultSetMetaData rsmd = rs.getMetaData();
			for(int i=rsmd.getColumnCount(); i>0; i--)
			{
				String fName = rsmd.getColumnName(i);
				
				Class<?> cType = clazz.getDeclaredField(fName).getType();
				
				String fType = cType.toString();
				fType = fType.substring(fType.lastIndexOf('.') + 1);
				
				Method rsm = rs.getClass().getMethod("get" + fType.substring(0, 1).toUpperCase() + fType.substring(1), String.class);
				Method m = t.getClass().getMethod("set" + fName.substring(0, 1).toUpperCase() + fName.substring(1), cType);

				m.invoke(t, rsm.invoke(rs, fName));
			}

//			Field[] fields = clazz.getDeclaredFields();
//			for (Field field : fields)
//			{
//				String fName = field.getName();
//				String fType = field.getType().toString();
//				fType = fType.substring(fType.lastIndexOf('.') + 1);
//
//				Method rsm = rs.getClass().getMethod("get" + fType.substring(0, 1).toUpperCase() + fType.substring(1), String.class);
//				Method m = t.getClass().getMethod("set" + fName.substring(0, 1).toUpperCase() + fName.substring(1), field.getType());
//
//				m.invoke(t, rsm.invoke(rs, fName));
//			}

			return t;
		}
		catch (InstantiationException e)
		{
			throw new BuildClassError(e);
		}
		catch (IllegalAccessException e)
		{
			throw new BuildClassError(e);
		}
		catch (SecurityException e)
		{
			throw new BuildClassError(e);
		}
		catch (IllegalArgumentException e)
		{
			throw new BuildClassError(e);
		}
		catch (InvocationTargetException e)
		{
			throw new BuildClassError(e);
		}
		catch (NoSuchMethodException e)
		{
			throw new BuildClassError(e);
		}
		catch (SQLException e)
		{
			throw new BuildClassError(e);
		}
		catch (NoSuchFieldException e)
		{
			throw new BuildClassError(e);
		}
	}

}
